﻿Namespace Processors

    Public Class Terrain
        Private G As GraphicsDeviceManager
        Private Device As GraphicsDevice
        Public GrassTexture As Texture2D
        Public SandTexture As Texture2D
        Public RockTexture As Texture2D
        Public SnowTexture As Texture2D
        Public HeightMap As Texture2D
        Public WaterBumpMap As Texture2D
        Public AmbientLightValue As Single
        Public LightDirection As Vector3
        Public WaterHeight As Single
        Public Water As Vector4
        Public WindDirection As Vector3
        Public WindForce As Single
        Private terrainWidth As Integer
        Private terrainLength As Integer
        Private heightData(,) As Single
        Private terrainVertexBuffer As VertexBuffer
        Private terrainIndexBuffer As IndexBuffer
        Private Indices() As Integer
        Private Vertices() As VertexMultiTextured
        Private WaterVertexBuffer As VertexBuffer
        Private RefractionRenderTarget As RenderTarget2D
        Private RefractionMap As Texture2D
        Private ReflectionRenderTarget As RenderTarget2D
        Private ReflectionMap As Texture2D

        Public Sub New(Graphics As GraphicsDeviceManager)
            G = Graphics
            Device = G.GraphicsDevice
        End Sub

        Public Sub Load()
            LoadVertices()
            SetUpWaterVertices()
        End Sub

        Private Sub LoadVertices()
            LoadHeightData()
            Dim terrainVertices() As VertexMultiTextured = SetUpTerrainVertices()
            Dim terrainIndices() As Integer = SetUpTerrainIndices()
            terrainVertices = CalculateNormals(terrainVertices, terrainIndices)
            CopyToTerrainBuffers(terrainVertices, terrainIndices)
        End Sub

        Private Sub LoadHeightData()
            Dim x As Integer, y As Integer
            Dim minimumHeight As Single = Single.MaxValue
            Dim maximumHeight As Single = Single.MinValue
            terrainWidth = heightMap.Width
            terrainLength = heightMap.Height
            Dim heightMapColors As Color() = New Color((terrainWidth * terrainLength) - 1) {}
            heightMap.GetData(Of Color)(heightMapColors)
            ReDim heightData(terrainWidth, terrainLength)
            For x = 0 To terrainWidth - 1
                For y = 0 To terrainLength - 1
                    heightData(x, y) = heightMapColors(x + y * terrainWidth).R
                    If heightData(x, y) < minimumHeight Then
                        minimumHeight = heightData(x, y)
                    End If
                    If heightData(x, y) > maximumHeight Then
                        maximumHeight = heightData(x, y)
                    End If
                Next y
            Next x
            For x = 0 To terrainWidth - 1
                For y = 0 To terrainLength - 1
                    heightData(x, y) = (heightData(x, y) - minimumHeight) / (maximumHeight - minimumHeight) * 30
                Next y
            Next x
        End Sub

        Private Function SetUpTerrainVertices() As VertexMultiTextured()
            Dim x As Integer, y As Integer
            Dim total As Single
            Dim terrainVertices As VertexMultiTextured() = New VertexMultiTextured(terrainWidth * terrainLength) {}
            vertices = New VertexMultiTextured(terrainWidth * terrainLength) {}
            For x = 0 To terrainWidth - 1
                For y = 0 To terrainLength - 1
                    terrainVertices(x + y * terrainWidth).Position = New Vector3(x, heightData(x, y), -y)
                    terrainVertices(x + y * terrainWidth).TextureCoordinate.X = CSng(x / 30)
                    terrainVertices(x + y * terrainWidth).TextureCoordinate.Y = CSng(y / 30)
                    terrainVertices(x + y * terrainWidth).TexWeights.X = MathHelper.Clamp(1 - Math.Abs(heightData(x, y) - 0) / 8, 0, 1)
                    terrainVertices(x + y * terrainWidth).TexWeights.Y = MathHelper.Clamp(1 - Math.Abs(heightData(x, y) - 12) / 6, 0, 1)
                    terrainVertices(x + y * terrainWidth).TexWeights.Z = MathHelper.Clamp(1 - Math.Abs(heightData(x, y) - 20) / 6, 0, 1)
                    terrainVertices(x + y * terrainWidth).TexWeights.W = MathHelper.Clamp(1 - Math.Abs(heightData(x, y) - 30) / 6, 0, 1)
                    total = terrainVertices(x + y * terrainWidth).TexWeights.X
                    total = total + terrainVertices(x + y * terrainWidth).TexWeights.Y
                    total = total + terrainVertices(x + y * terrainWidth).TexWeights.Z
                    total = total + terrainVertices(x + y * terrainWidth).TexWeights.W
                    terrainVertices(x + y * terrainWidth).TexWeights.X = terrainVertices(x + y * terrainWidth).TexWeights.X / total
                    terrainVertices(x + y * terrainWidth).TexWeights.Y = terrainVertices(x + y * terrainWidth).TexWeights.Y / total
                    terrainVertices(x + y * terrainWidth).TexWeights.Z = terrainVertices(x + y * terrainWidth).TexWeights.Z / total
                    terrainVertices(x + y * terrainWidth).TexWeights.W = terrainVertices(x + y * terrainWidth).TexWeights.W / total
                Next y
            Next x
            Return terrainVertices
        End Function

        Private Function SetUpTerrainIndices() As Integer()
            Dim x As Integer, y As Integer
            Dim counter As Integer = -1
            Dim lowerLeft As Integer
            Dim lowerRight As Integer
            Dim topLeft As Integer
            Dim topRight As Integer
            ReDim indices((terrainWidth - 1) * (terrainLength - 1) * 6)
            For x = 0 To terrainWidth - 2
                For y = 0 To terrainLength - 2
                    lowerLeft = x + y * terrainWidth
                    lowerRight = (x + 1) + y * terrainWidth
                    topLeft = x + (y + 1) * terrainWidth
                    topRight = (x + 1) + (y + 1) * terrainWidth
                    counter = counter + 1
                    indices(counter) = topLeft
                    counter = counter + 1
                    indices(counter) = lowerRight
                    counter = counter + 1
                    indices(counter) = lowerLeft
                    counter = counter + 1
                    indices(counter) = topLeft
                    counter = counter + 1
                    indices(counter) = topRight
                    counter = counter + 1
                    indices(counter) = lowerRight
                Next y
            Next x
            Return indices
        End Function

        Private Function CalculateNormals(Vertices As VertexMultiTextured(), Indices As Integer()) As VertexMultiTextured()
            Dim i As Integer
            Dim index1 As Integer
            Dim index2 As Integer
            Dim index3 As Integer
            Dim side1 As Vector3
            Dim side2 As Vector3
            Dim normal As Vector3
            For i = 0 To Vertices.Length - 1
                Vertices(i).Normal = New Vector3(0, 0, 0)
            Next i
            For i = 0 To (Indices.Length / 3) - 1
                index1 = Indices(i * 3)
                index2 = Indices(i * 3 + 1)
                index3 = Indices(i * 3 + 2)
                side1 = Vertices(index1).Position - Vertices(index3).Position
                side2 = Vertices(index1).Position - Vertices(index2).Position
                normal = Vector3.Cross(side1, side2)
                Vertices(index1).Normal = Vertices(index1).Normal + normal
                Vertices(index2).Normal = Vertices(index2).Normal + normal
                Vertices(index3).Normal = Vertices(index3).Normal + normal
            Next i
            For i = 0 To Vertices.Length - 1
                Vertices(i).Normal.Normalize()
            Next i
            Return Vertices
        End Function

        Private Sub CopyToTerrainBuffers(Vertices As VertexMultiTextured(), Indices As Integer())
            Dim VertexDeclaration As VertexDeclaration = New VertexDeclaration(VertexMultiTextured.VertexElements)
            terrainVertexBuffer = New VertexBuffer(Device, VertexDeclaration, Vertices.Length, BufferUsage.WriteOnly)
            terrainVertexBuffer.SetData(Of VertexMultiTextured)(Vertices.ToArray)
            terrainIndexBuffer = New IndexBuffer(Device, IndexElementSize.ThirtyTwoBits, Indices.Length, BufferUsage.WriteOnly)
            terrainIndexBuffer.SetData(Indices)
        End Sub

        Public Sub DrawTerrain(ByRef Device2 As GraphicsDevice, ByRef Effect As Effect, ViewMatrix As Matrix, ProjMatrix As Matrix)
            Effect.CurrentTechnique = Effect.Techniques("MultiTextured")
            Effect.Parameters("xTexture0").SetValue(SandTexture)
            Effect.Parameters("xTexture1").SetValue(GrassTexture)
            Effect.Parameters("xTexture2").SetValue(RockTexture)
            Effect.Parameters("xTexture3").SetValue(SnowTexture)
            Dim worldMatrix As Matrix = Matrix.Identity
            Effect.Parameters("xWorld").SetValue(worldMatrix)
            Effect.Parameters("xView").SetValue(ViewMatrix)
            Effect.Parameters("xProjection").SetValue(ProjMatrix)
            Effect.Parameters("xEnableLighting").SetValue(True)
            Effect.Parameters("xAmbient").SetValue(AmbientLightValue)
            Effect.Parameters("xLightDirection").SetValue(LightDirection)
            For Each pass As EffectPass In Effect.CurrentTechnique.Passes
                pass.Apply()
                Device2.Indices = terrainIndexBuffer
                Device2.SetVertexBuffer(terrainVertexBuffer)
                Device2.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, Vertices.Length, 0, CInt(Indices.Length / 3))
            Next
        End Sub

        Public Sub DrawRefractionMap(ByRef Device2 As GraphicsDevice, ByRef Effect As Effect, ByRef Camera As Cameras.FirstPerson, SkyDome As MapObjects.SkyDome)
            Dim refractionPlane As Plane = CreatePlane(CSng(WaterHeight + 1.5), New Vector3(0, -1, 0), Camera.GetViewMatrix, False)
            RefractionRenderTarget = New RenderTarget2D(Device, Device.Viewport.Width, Device.Viewport.Height)
            Effect.Parameters("xClipPlane").SetValue(New Vector4(refractionPlane.Normal, refractionPlane.D))
            Effect.Parameters("xClipping").SetValue(True)
            Device2.SetRenderTarget(RefractionRenderTarget)
            Device2.Clear(ClearOptions.Target, Color.Black, 1, 0)
            DrawTerrain(Device2, Effect, Camera.GetViewMatrix, Camera.GetProjMatrix)
            Device2.SetRenderTarget(Nothing)
            Effect.Parameters("xClipping").SetValue(False)
            RefractionMap = RefractionRenderTarget
            'Dim FS As New System.IO.FileStream(System.AppDomain.CurrentDomain.BaseDirectory & "Content\RefractionMap.jpg", IO.FileMode.OpenOrCreate)
            'RefractionMap.SaveAsJpeg(FS, RefractionMap.Width, RefractionMap.Height)
            'FS.Close()
            'FS = Nothing
        End Sub

        Public Sub DrawReflectionMap(ByRef Device2 As GraphicsDevice, ByRef Effect As Effect, ByRef Camera As Cameras.FirstPerson, SkyDome As MapObjects.SkyDome, MapObjects() As MapObjects.Models)
            Dim i As Integer
            Dim reflectionPlane As Plane = CreatePlane(CSng(WaterHeight - 0.5), New Vector3(0, -1, 0), Camera.GetReflectionViewMatrix(WaterHeight), True)
            ReflectionRenderTarget = New RenderTarget2D(Device, Device.Viewport.Width, Device.Viewport.Height)
            Effect.Parameters("xClipPlane").SetValue(New Vector4(reflectionPlane.Normal, reflectionPlane.D))
            Effect.Parameters("xClipping").SetValue(True)
            Device2.SetRenderTarget(ReflectionRenderTarget)
            Device2.Clear(ClearOptions.Target, Color.Black, 1, 0)
            SkyDome.DrawReflection(Device2, Effect, Camera, LightDirection, AmbientLightValue, WaterHeight + 1.5)
            DrawTerrain(Device2, Effect, Camera.GetReflectionViewMatrix(WaterHeight + 1.5), Camera.GetProjMatrix)
            For i = 0 To MapObjects.Count - 1
                MapObjects(i).Draw(Device2, Effect, Camera, LightDirection, AmbientLightValue)
            Next i
            Effect.Parameters("xClipping").SetValue(False)
            Device2.SetRenderTarget(Nothing)
            ReflectionMap = ReflectionRenderTarget
            'Dim FS As New System.IO.FileStream(System.AppDomain.CurrentDomain.BaseDirectory & "Content\ReflectionMap.jpg", IO.FileMode.OpenOrCreate)
            'ReflectionMap.SaveAsJpeg(FS, ReflectionMap.Width, ReflectionMap.Height)
            'FS.Close()
            'FS = Nothing
        End Sub

        Public Sub DrawWater(ByVal gameTime As GameTime, Device2 As GraphicsDevice, ByRef Effect As Effect, Camera As Cameras.FirstPerson)
            Dim time As Single = CSng(gameTime.TotalGameTime.TotalMilliseconds / 100)
            'Dim RS As RasterizerState
            'Dim RS2 As New RasterizerState
            'RS2.CullMode = CullMode.CullClockwiseFace
            'RS = Device2.RasterizerState
            'Device2.RasterizerState = RS2
            Effect.CurrentTechnique = Effect.Techniques("Water")
            Dim worldMatrix As Matrix = Matrix.Identity
            Effect.Parameters("xWorld").SetValue(worldMatrix)
            Effect.Parameters("xView").SetValue(Camera.GetViewMatrix)
            Effect.Parameters("xReflectionView").SetValue(Camera.GetReflectionViewMatrix(WaterHeight))
            Effect.Parameters("xProjection").SetValue(Camera.GetProjMatrix)
            Effect.Parameters("xReflectionMap").SetValue(ReflectionMap)
            Effect.Parameters("xRefractionMap").SetValue(RefractionMap)
            Effect.Parameters("xWaterBumpMap").SetValue(WaterBumpMap)
            'Dim FS As New System.IO.FileStream(System.AppDomain.CurrentDomain.BaseDirectory & "Content\WaterSample.jpg", IO.FileMode.OpenOrCreate)
            'WaterBumpMap.SaveAsJpeg(FS, WaterBumpMap.Width, WaterBumpMap.Height)
            'FS.Close()
            'FS = Nothing
            Effect.Parameters("xWaveLength").SetValue(CSng(0.0)) '0.1
            Effect.Parameters("xWaveHeight").SetValue(CSng(0.0)) '0.3
            Effect.Parameters("xCamPos").SetValue(Camera.CameraPosition)
            Effect.Parameters("xTime").SetValue(time)
            Effect.Parameters("xWindForce").SetValue(WindForce)
            Effect.Parameters("xWindDirection").SetValue(WindDirection)
            'Effect.Parameters("xEnableLighting").SetValue(True)
            'Effect.Parameters("xAmbient").SetValue(AmbientLightValue)
            'Effect.Parameters("xLightDirection").SetValue(LightDirection)
            For Each pass As EffectPass In Effect.CurrentTechnique.Passes
                pass.Apply()
                Device2.SetVertexBuffer(WaterVertexBuffer)
                Device2.DrawPrimitives(PrimitiveType.TriangleList, 0, CInt(WaterVertexBuffer.VertexCount / 3))
            Next
            'Device2.RasterizerState = RS
        End Sub

        Private Sub SetUpWaterVertices()
            Dim Vectors(3) As Vector3
            'Vectors(0) = New Vector3(Water.X, WaterHeight, Water.Y)
            'Vectors(1) = New Vector3(Water.X + Water.Z, WaterHeight, Water.Y)
            'Vectors(2) = New Vector3(Water.X, WaterHeight, Water.Y + Water.W)
            'Vectors(3) = New Vector3(Water.X + Water.Z, WaterHeight, Water.Y + Water.W)
            Vectors(0) = New Vector3(0 - terrainWidth, WaterHeight, 0 + terrainLength)
            Vectors(1) = New Vector3(terrainWidth * 2, WaterHeight, 0 + terrainLength)
            Vectors(2) = New Vector3(0 - terrainWidth, WaterHeight, -terrainLength * 2)
            Vectors(3) = New Vector3(terrainWidth * 2, WaterHeight, -terrainLength * 2)

            'Vectors(0) = New Vector3(0, WaterHeight, 0)
            'Vectors(1) = New Vector3(0, WaterHeight, -128)
            'Vectors(2) = New Vector3(128, WaterHeight, -128)
            'Vectors(3) = New Vector3(128, WaterHeight, 0)
            Dim waterVertices(5) As VertexPositionTexture

            waterVertices(0) = New VertexPositionTexture(Vectors(3), New Vector2(0, 0))
            waterVertices(1) = New VertexPositionTexture(Vectors(1), New Vector2(1, 0))
            waterVertices(2) = New VertexPositionTexture(Vectors(0), New Vector2(1, 1))

            waterVertices(3) = New VertexPositionTexture(Vectors(2), New Vector2(0, 0))
            waterVertices(4) = New VertexPositionTexture(Vectors(3), New Vector2(1, 1))
            waterVertices(5) = New VertexPositionTexture(Vectors(0), New Vector2(0, 1))

            waterVertexBuffer = New VertexBuffer(G.GraphicsDevice, VertexPositionTexture.VertexDeclaration, waterVertices.Count, BufferUsage.WriteOnly)
            waterVertexBuffer.SetData(waterVertices)
        End Sub

        Private Function CreatePlane(height As Single, planeNormalDirection As Vector3, currentViewMatrix As Matrix, clipSide As Boolean) As Plane
            planeNormalDirection.Normalize()
            Dim planeCoeffs As Vector4 = New Vector4(planeNormalDirection, height)
            If clipSide = True Then
                planeCoeffs = planeCoeffs * -1
            End If
            Dim finalPlane As Plane = New Plane(planeCoeffs)
            Return finalPlane
        End Function

    End Class

End Namespace